﻿using System;
using System.Runtime.InteropServices;
using winPEAS.TaskScheduler.TaskEditor.Native;

namespace winPEAS.TaskScheduler.V1
{
#pragma warning disable CS0618 // Type or member is obsolete

    #region class HRESULT -- Values peculiar to the task scheduler.
    internal class HResult
    {
        // The task is ready to run at its next scheduled time.
        public const int SCHED_S_TASK_READY = 0x00041300;
        // The task is currently running.
        public const int SCHED_S_TASK_RUNNING = 0x00041301;
        // The task will not run at the scheduled times because it has been disabled.
        public const int SCHED_S_TASK_DISABLED = 0x00041302;
        // The task has not yet run.
        public const int SCHED_S_TASK_HAS_NOT_RUN = 0x00041303;
        // There are no more runs scheduled for this task.
        public const int SCHED_S_TASK_NO_MORE_RUNS = 0x00041304;
        // One or more of the properties that are needed to run this task on a schedule have not been set.
        public const int SCHED_S_TASK_NOT_SCHEDULED = 0x00041305;
        // The last run of the task was terminated by the user.
        public const int SCHED_S_TASK_TERMINATED = 0x00041306;
        // Either the task has no triggers or the existing triggers are disabled or not set.
        public const int SCHED_S_TASK_NO_VALID_TRIGGERS = 0x00041307;
        // Event triggers don't have set run times.
        public const int SCHED_S_EVENT_TRIGGER = 0x00041308;
        // Trigger not found.
        public const int SCHED_E_TRIGGER_NOT_FOUND = unchecked((int)0x80041309);
        // One or more of the properties that are needed to run this task have not been set.
        public const int SCHED_E_TASK_NOT_READY = unchecked((int)0x8004130A);
        // There is no running instance of the task to terminate.
        public const int SCHED_E_TASK_NOT_RUNNING = unchecked((int)0x8004130B);
        // The Task Scheduler Service is not installed on this computer.
        public const int SCHED_E_SERVICE_NOT_INSTALLED = unchecked((int)0x8004130C);
        // The task object could not be opened.
        public const int SCHED_E_CANNOT_OPEN_TASK = unchecked((int)0x8004130D);
        // The object is either an invalid task object or is not a task object.
        public const int SCHED_E_INVALID_TASK = unchecked((int)0x8004130E);
        // No account information could be found in the Task Scheduler security database for the task indicated.
        public const int SCHED_E_ACCOUNT_INFORMATION_NOT_SET = unchecked((int)0x8004130F);
        // Unable to establish existence of the account specified.
        public const int SCHED_E_ACCOUNT_NAME_NOT_FOUND = unchecked((int)0x80041310);
        // Corruption was detected in the Task Scheduler security database; the database has been reset.
        public const int SCHED_E_ACCOUNT_DBASE_CORRUPT = unchecked((int)0x80041311);
        // Task Scheduler security services are available only on Windows NT.
        public const int SCHED_E_NO_SECURITY_SERVICES = unchecked((int)0x80041312);
        // The task object version is either unsupported or invalid.
        public const int SCHED_E_UNKNOWN_OBJECT_VERSION = unchecked((int)0x80041313);
        // The task has been configured with an unsupported combination of account settings and run time options.
        public const int SCHED_E_UNSUPPORTED_ACCOUNT_OPTION = unchecked((int)0x80041314);
        // The Task Scheduler Service is not running.
        public const int SCHED_E_SERVICE_NOT_RUNNING = unchecked((int)0x80041315);
        // The Task Scheduler service must be configured to run in the System account to function properly.  Individual tasks may be configured to run in other accounts.
        public const int SCHED_E_SERVICE_NOT_LOCALSYSTEM = unchecked((int)0x80041316);
    }
    #endregion

    #region Enums

    /// <summary>
    /// Options for a task, used for the Flags property of a Task. Uses the
    /// "Flags" attribute, so these values are combined with |. 
    /// Some flags are documented as Windows 95 only, but they have a
    /// user interface in Windows XP so that may not be true.
    /// </summary>
    [Flags]
    internal enum TaskFlags
    {
        /// <summary>
        /// The interactive flag is set if the task is intended to be displayed to the user. 
        /// If the flag is not set, no user interface associated with the task is presented
        /// to the user when the task is executed.
        /// </summary>
        Interactive = 0x1,
        /// <summary>
        /// The task will be deleted when there are no more scheduled run times.
        /// </summary>
        DeleteWhenDone = 0x2,
        /// <summary>
        /// The task is disabled. This is useful to temporarily prevent a task from running
        /// at the scheduled time(s).
        /// </summary>
        Disabled = 0x4,
        /// <summary>
        /// The task begins only if the computer is not in use at the scheduled start time. Windows 95 only.
        /// </summary>
        StartOnlyIfIdle = 0x10,
        /// <summary>
        /// The task terminates if the computer makes an idle to non-idle transition while the task is running.
        /// The computer is not considered idle until the IdleWait triggers' time elapses with no user input.
        /// Windows 95 only. For information regarding idle triggers, see <see cref="IdleTrigger"/>.
        /// </summary>
        KillOnIdleEnd = 0x20,
        /// <summary>
        /// The task does not start if its target computer is running on battery power. Windows 95 only.
        /// </summary>
        DontStartIfOnBatteries = 0x40,
        /// <summary>
        /// The task ends, and the associated application quits if the task's target computer switches
        /// to battery power. Windows 95 only.
        /// </summary>
        KillIfGoingOnBatteries = 0x80,
        /// <summary>
        /// The task runs only if the system is docked. Windows 95 only.
        /// </summary>
        RunOnlyIfDocked = 0x100,
        /// <summary>
        /// The work item created will be hidden.
        /// </summary>
        Hidden = 0x200,
        /// <summary>
        /// The task runs only if there is currently a valid Internet connection.
        /// This feature is currently not implemented.
        /// </summary>
        RunIfConnectedToInternet = 0x400,
        /// <summary>
        /// The task starts again if the computer makes a non-idle to idle transition before all the
        /// task's task_triggers elapse. (Use this flag in conjunction with KillOnIdleEnd.) Windows 95 only.
        /// </summary>
        RestartOnIdleResume = 0x800,
        /// <summary>
        /// The task runs only if the SYSTEM account is available.
        /// </summary>
        SystemRequired = 0x1000,
        /// <summary>
        /// The task runs only if the user specified in SetAccountInformation is logged on interactively. 
        /// This flag has no effect on work items set to run in the local account.
        /// </summary>
        RunOnlyIfLoggedOn = 0x2000
    }

    /// <summary>
    /// Status values returned for a task.  Some values have been determined to occur although
    /// they do no appear in the Task Scheduler system documentation.
    /// </summary>
    internal enum TaskStatus
    {
        /// <summary>The task is ready to run at its next scheduled time.</summary>
        Ready = HResult.SCHED_S_TASK_READY,
        /// <summary>The task is currently running.</summary>
        Running = HResult.SCHED_S_TASK_RUNNING,
        /// <summary>One or more of the properties that are needed to run this task on a schedule have not been set. </summary>
        NotScheduled = HResult.SCHED_S_TASK_NOT_SCHEDULED,
        /// <summary>The task has not yet run.</summary>
        NeverRun = HResult.SCHED_S_TASK_HAS_NOT_RUN,
        /// <summary>The task will not run at the scheduled times because it has been disabled.</summary>
        Disabled = HResult.SCHED_S_TASK_DISABLED,
        /// <summary>There are no more runs scheduled for this task.</summary>
        NoMoreRuns = HResult.SCHED_S_TASK_NO_MORE_RUNS,
        /// <summary>The last run of the task was terminated by the user.</summary>
        Terminated = HResult.SCHED_S_TASK_TERMINATED,
        /// <summary>Either the task has no triggers or the existing triggers are disabled or not set.</summary>
        NoTriggers = HResult.SCHED_S_TASK_NO_VALID_TRIGGERS,
        /// <summary>Event triggers don't have set run times.</summary>
        NoTriggerTime = HResult.SCHED_S_EVENT_TRIGGER
    }

    /// <summary>Valid types of triggers</summary>
    internal enum TaskTriggerType
    {
        /// <summary>Trigger is set to run the task a single time. </summary>
        RunOnce = 0,
        /// <summary>Trigger is set to run the task on a daily interval. </summary>
        RunDaily = 1,
        /// <summary>Trigger is set to run the work item on specific days of a specific week of a specific month. </summary>
        RunWeekly = 2,
        /// <summary>Trigger is set to run the task on a specific day(s) of the month.</summary>
        RunMonthly = 3,
        /// <summary>Trigger is set to run the task on specific days, weeks, and months.</summary>
        RunMonthlyDOW = 4,
        /// <summary>Trigger is set to run the task if the system remains idle for the amount of time specified by the idle wait time of the task.</summary>
        OnIdle = 5,
        /// <summary>Trigger is set to run the task at system startup.</summary>
        OnSystemStart = 6,
        /// <summary>Trigger is set to run the task when a user logs on. </summary>
        OnLogon = 7
    }

    [Flags]
    internal enum TaskTriggerFlags : uint
    {
        HasEndDate = 0x1,
        KillAtDurationEnd = 0x2,
        Disabled = 0x4
    }

    #endregion

    #region Structs

    [StructLayout(LayoutKind.Sequential)]
    internal struct Daily
    {
        public ushort DaysInterval;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct Weekly
    {
        public ushort WeeksInterval;
        public DaysOfTheWeek DaysOfTheWeek;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct MonthlyDate
    {
        public uint Days;
        public MonthsOfTheYear Months;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct MonthlyDOW
    {
        public ushort WhichWeek;
        public DaysOfTheWeek DaysOfTheWeek;
        public MonthsOfTheYear Months;

        public WhichWeek V2WhichWeek
        {
            get
            {
                return (WhichWeek)(1 << ((short)WhichWeek - 1));
            }
            set
            {
                int idx = Array.IndexOf(new short[] { 0x1, 0x2, 0x4, 0x8, 0x10 }, (short)value);
                if (idx >= 0)
                    WhichWeek = (ushort)(idx + 1);
                else
                    throw new NotV1SupportedException("Only a single week can be set with Task Scheduler 1.0.");
            }
        }
    }

    [StructLayout(LayoutKind.Explicit)]
    internal struct TriggerTypeData
    {
        [FieldOffset(0)]
        public Daily daily;
        [FieldOffset(0)]
        public Weekly weekly;
        [FieldOffset(0)]
        public MonthlyDate monthlyDate;
        [FieldOffset(0)]
        public MonthlyDOW monthlyDOW;
    }

    [StructLayout(LayoutKind.Sequential)]
    internal struct TaskTrigger
    {
        public ushort TriggerSize;             // Structure size.
        public ushort Reserved1;               // Reserved. Must be zero.
        public ushort BeginYear;               // Trigger beginning date year.
        public ushort BeginMonth;              // Trigger beginning date month.
        public ushort BeginDay;                // Trigger beginning date day.
        public ushort EndYear;                 // Optional trigger ending date year.
        public ushort EndMonth;                // Optional trigger ending date month.
        public ushort EndDay;                  // Optional trigger ending date day.
        public ushort StartHour;               // Run bracket start time hour.
        public ushort StartMinute;             // Run bracket start time minute.
        public uint MinutesDuration;           // Duration of run bracket.
        public uint MinutesInterval;           // Run bracket repetition interval.
        public TaskTriggerFlags Flags;         // Trigger flags.
        public TaskTriggerType Type;           // Trigger type.
        public TriggerTypeData Data;           // Trigger data peculiar to this type (union).
        public ushort Reserved2;               // Reserved. Must be zero.
        public ushort RandomMinutesInterval;   // Maximum number of random minutes after start time.

        public DateTime BeginDate
        {
            get { try { return BeginYear == 0 ? DateTime.MinValue : new DateTime(BeginYear, BeginMonth, BeginDay, StartHour, StartMinute, 0, DateTimeKind.Unspecified); } catch { return DateTime.MinValue; } }
            set
            {
                if (value != DateTime.MinValue)
                {
                    DateTime local = value.Kind == DateTimeKind.Utc ? value.ToLocalTime() : value;
                    BeginYear = (ushort)local.Year;
                    BeginMonth = (ushort)local.Month;
                    BeginDay = (ushort)local.Day;
                    StartHour = (ushort)local.Hour;
                    StartMinute = (ushort)local.Minute;
                }
                else
                    BeginYear = BeginMonth = BeginDay = StartHour = StartMinute = 0;
            }
        }

        public DateTime? EndDate
        {
            get { try { return EndYear == 0 ? (DateTime?)null : new DateTime(EndYear, EndMonth, EndDay); } catch { return DateTime.MaxValue; } }
            set
            {
                if (value.HasValue)
                {
                    EndYear = (ushort)value.Value.Year;
                    EndMonth = (ushort)value.Value.Month;
                    EndDay = (ushort)value.Value.Day;
                    Flags |= TaskTriggerFlags.HasEndDate;
                }
                else
                {
                    EndYear = EndMonth = EndDay = 0;
                    Flags &= ~TaskTriggerFlags.HasEndDate;
                }
            }
        }

        public override string ToString() => $"Trigger Type: {Type};\n> Start: {BeginDate}; End: {(EndYear == 0 ? "null" : EndDate?.ToString())};\n> DurMin: {MinutesDuration}; DurItv: {MinutesInterval};\n>";
    }

    #endregion

    [ComImport, Guid("148BD527-A2AB-11CE-B11F-00AA00530503"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), System.Security.SuppressUnmanagedCodeSecurity, CoClass(typeof(CTaskScheduler))]
    internal interface ITaskScheduler
    {
        void SetTargetComputer([In, MarshalAs(UnmanagedType.LPWStr)] string Computer);
        CoTaskMemString GetTargetComputer();
        [return: MarshalAs(UnmanagedType.Interface)]
        IEnumWorkItems Enum();
        [return: MarshalAs(UnmanagedType.Interface)]
        ITask Activate([In, MarshalAs(UnmanagedType.LPWStr)][NotNull] string Name, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid);
        void Delete([In, MarshalAs(UnmanagedType.LPWStr)][NotNull] string Name);
        [return: MarshalAs(UnmanagedType.Interface)]
        ITask NewWorkItem([In, MarshalAs(UnmanagedType.LPWStr)][NotNull] string TaskName, [In, MarshalAs(UnmanagedType.LPStruct)] Guid rclsid, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid);
        void AddWorkItem([In, MarshalAs(UnmanagedType.LPWStr)][NotNull] string TaskName, [In, MarshalAs(UnmanagedType.Interface)] ITask WorkItem);
        void IsOfType([In, MarshalAs(UnmanagedType.LPWStr)][NotNull] string TaskName, [In, MarshalAs(UnmanagedType.LPStruct)] Guid riid);
    }

    [Guid("148BD528-A2AB-11CE-B11F-00AA00530503"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), System.Security.SuppressUnmanagedCodeSecurity]
    internal interface IEnumWorkItems
    {
        [PreserveSig()]
        //int Next([In] uint celt, [Out, MarshalAs(UnmanagedType.LPArray, ArraySubType = UnmanagedType.LPWStr, SizeParamIndex = 0)] out string[] rgpwszNames, [Out] out uint pceltFetched);
        int Next([In] uint RequestCount, [Out] out IntPtr Names, [Out] out uint Fetched);
        void Skip([In] uint Count);
        void Reset();
        [return: MarshalAs(UnmanagedType.Interface)]
        IEnumWorkItems Clone();
    }

#if WorkItem
	// The IScheduledWorkItem interface is actually never used because ITask inherits all of its
	// methods.  As ITask is the only kind of WorkItem (in 2002) it is the only interface we need.
	[Guid("a6b952f0-a4b1-11d0-997d-00aa006887ec"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown)]
	internal interface IScheduledWorkItem
	{
		void CreateTrigger([Out] out ushort NewTriggerIndex, [Out, MarshalAs(UnmanagedType.Interface)] out ITaskTrigger Trigger);
		void DeleteTrigger([In] ushort TriggerIndex);
		void GetTriggerCount([Out] out ushort Count);
		void GetTrigger([In] ushort TriggerIndex, [Out, MarshalAs(UnmanagedType.Interface)] out ITaskTrigger Trigger);
		void GetTriggerString([In] ushort TriggerIndex, out System.IntPtr TriggerString);
		void GetRunTimes([In, MarshalAs(UnmanagedType.Struct)] SystemTime Begin, [In, MarshalAs(UnmanagedType.Struct)] SystemTime End, ref ushort Count, [Out] out System.IntPtr TaskTimes);
		void GetNextRunTime([In, Out, MarshalAs(UnmanagedType.Struct)] ref SystemTime NextRun);
		void SetIdleWait([In] ushort IdleMinutes, [In] ushort DeadlineMinutes);
		void GetIdleWait([Out] out ushort IdleMinutes, [Out] out ushort DeadlineMinutes);
		void Run();
		void Terminate();
		void EditWorkItem([In] uint hParent, [In] uint dwReserved);
		void GetMostRecentRunTime([In, Out, MarshalAs(UnmanagedType.Struct)] ref SystemTime LastRun);
		void GetStatus([Out, MarshalAs(UnmanagedType.Error)] out int Status);
		void GetExitCode([Out] out uint ExitCode);
		void SetComment([In, MarshalAs(UnmanagedType.LPWStr)] string Comment);
		void GetComment(out System.IntPtr Comment);
		void SetCreator([In, MarshalAs(UnmanagedType.LPWStr)] string Creator);
		void GetCreator(out System.IntPtr Creator);
		void SetWorkItemData([In] ushort DataLen, [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex=0, ArraySubType=UnmanagedType.U1)] byte[] Data);
		void GetWorkItemData([Out] out ushort DataLen, [Out] out System.IntPtr Data);
		void SetErrorRetryCount([In] ushort RetryCount);
		void GetErrorRetryCount([Out] out ushort RetryCount);
		void SetErrorRetryInterval([In] ushort RetryInterval);
		void GetErrorRetryInterval([Out] out ushort RetryInterval);
		void SetFlags([In] uint Flags);
		void GetFlags([Out] out uint Flags);
		void SetAccountInformation([In, MarshalAs(UnmanagedType.LPWStr)] string AccountName, [In, MarshalAs(UnmanagedType.LPWStr)] string Password);
		void GetAccountInformation(out System.IntPtr AccountName);
	}
#endif

    [ComImport, Guid("148BD524-A2AB-11CE-B11F-00AA00530503"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), System.Security.SuppressUnmanagedCodeSecurity, CoClass(typeof(CTask))]
    internal interface ITask
    {
        [return: MarshalAs(UnmanagedType.Interface)]
        ITaskTrigger CreateTrigger([Out] out ushort NewTriggerIndex);
        void DeleteTrigger([In] ushort TriggerIndex);
        [return: MarshalAs(UnmanagedType.U2)]
        ushort GetTriggerCount();
        [return: MarshalAs(UnmanagedType.Interface)]
        ITaskTrigger GetTrigger([In] ushort TriggerIndex);
        CoTaskMemString GetTriggerString([In] ushort TriggerIndex);
        void GetRunTimes([In, MarshalAs(UnmanagedType.Struct)] ref NativeMethods.SYSTEMTIME Begin, [In, MarshalAs(UnmanagedType.Struct)] ref NativeMethods.SYSTEMTIME End, ref ushort Count, [In, Out] ref IntPtr TaskTimes);
        [return: MarshalAs(UnmanagedType.Struct)]
        NativeMethods.SYSTEMTIME GetNextRunTime();
        void SetIdleWait([In] ushort IdleMinutes, [In] ushort DeadlineMinutes);
        void GetIdleWait([Out] out ushort IdleMinutes, [Out] out ushort DeadlineMinutes);
        void Run();
        void Terminate();
        void EditWorkItem([In] IntPtr hParent, [In] uint dwReserved);
        [return: MarshalAs(UnmanagedType.Struct)]
        NativeMethods.SYSTEMTIME GetMostRecentRunTime();
        TaskStatus GetStatus();
        uint GetExitCode();
        void SetComment([In, MarshalAs(UnmanagedType.LPWStr)] string Comment);
        CoTaskMemString GetComment();
        void SetCreator([In, MarshalAs(UnmanagedType.LPWStr)] string Creator);
        CoTaskMemString GetCreator();
        void SetWorkItemData([In] ushort DataLen, [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0, ArraySubType = UnmanagedType.U1)] byte[] Data);
        void GetWorkItemData(out ushort DataLen, [Out] out IntPtr Data);
        void SetErrorRetryCount([In] ushort RetryCount);
        ushort GetErrorRetryCount();
        void SetErrorRetryInterval([In] ushort RetryInterval);
        ushort GetErrorRetryInterval();
        void SetFlags([In] TaskFlags Flags);
        TaskFlags GetFlags();
        void SetAccountInformation([In, MarshalAs(UnmanagedType.LPWStr)] string AccountName, [In] IntPtr Password);
        CoTaskMemString GetAccountInformation();
        void SetApplicationName([In, MarshalAs(UnmanagedType.LPWStr)] string ApplicationName);
        CoTaskMemString GetApplicationName();
        void SetParameters([In, MarshalAs(UnmanagedType.LPWStr)] string Parameters);
        CoTaskMemString GetParameters();
        void SetWorkingDirectory([In, MarshalAs(UnmanagedType.LPWStr)] string WorkingDirectory);
        CoTaskMemString GetWorkingDirectory();
        void SetPriority([In] uint Priority);
        uint GetPriority();
        void SetTaskFlags([In] uint Flags);
        uint GetTaskFlags();
        void SetMaxRunTime([In] uint MaxRunTimeMS);
        uint GetMaxRunTime();
    }

    [Guid("148BD52B-A2AB-11CE-B11F-00AA00530503"), InterfaceType(ComInterfaceType.InterfaceIsIUnknown), System.Security.SuppressUnmanagedCodeSecurity]
    internal interface ITaskTrigger
    {
        void SetTrigger([In, Out, MarshalAs(UnmanagedType.Struct)] ref TaskTrigger Trigger);
        [return: MarshalAs(UnmanagedType.Struct)]
        TaskTrigger GetTrigger();
        CoTaskMemString GetTriggerString();
    }

    [ComImport, Guid("148BD52A-A2AB-11CE-B11F-00AA00530503"), System.Security.SuppressUnmanagedCodeSecurity, ClassInterface(ClassInterfaceType.None)]
    internal class CTaskScheduler
    {
    }

    [ComImport, Guid("148BD520-A2AB-11CE-B11F-00AA00530503"), System.Security.SuppressUnmanagedCodeSecurity, ClassInterface(ClassInterfaceType.None)]
    internal class CTask
    {
    }

    internal sealed class CoTaskMemString : SafeHandle
    {
        public CoTaskMemString() : base(IntPtr.Zero, true) { }
        public CoTaskMemString(IntPtr handle) : this() { SetHandle(handle); }
        public CoTaskMemString(string text) : this() { SetHandle(Marshal.StringToCoTaskMemUni(text)); }

        public static implicit operator string(CoTaskMemString cmem) => cmem.ToString();

        public override bool IsInvalid => handle == IntPtr.Zero;

        protected override bool ReleaseHandle()
        {
            Marshal.FreeCoTaskMem(handle);
            return true;
        }

        public override string ToString() => Marshal.PtrToStringUni(handle);
    }
}
